package tech.yixiyun.framework.kuafu.db;

import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.IdUtil;
import com.fasterxml.jackson.databind.JavaType;
import tech.yixiyun.framework.kuafu.config.AppConfig;
import tech.yixiyun.framework.kuafu.config.ConfigKey;
import tech.yixiyun.framework.kuafu.db.datasource.DataSourceContext;
import tech.yixiyun.framework.kuafu.db.datasource.DbType;
import tech.yixiyun.framework.kuafu.db.session.DbSession;
import tech.yixiyun.framework.kuafu.db.session.DbSessionContext;
import tech.yixiyun.framework.kuafu.db.sql.*;
import tech.yixiyun.framework.kuafu.db.transaction.TransactionLevel;
import tech.yixiyun.framework.kuafu.domain.BaseDomain;
import tech.yixiyun.framework.kuafu.domain.ColumnDefinition;
import tech.yixiyun.framework.kuafu.domain.DomainContext;
import tech.yixiyun.framework.kuafu.domain.DomainDefinition;
import tech.yixiyun.framework.kuafu.domain.annotation.DomainType;
import tech.yixiyun.framework.kuafu.kits.StringKit;

import java.io.Serializable;
import java.lang.reflect.Array;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.LinkedList;
import java.util.List;
import java.util.function.Supplier;

/**
 * 操作数据库的工具
 *
 * @author Yixiyun
 * @version 1.0
 * @date 2021-04-23 16:47
 */
public class DbKit {


    /**
     * 基于DbUtils提供的执行工具
     */
    public static final SqlRunner RUNNER = new SqlRunner();

    /**
     * 开启事务,如果当前连接已开启过了，再次调用该方法无效
     * @param connection
     * @return 如果之前未开启，本次开启后会返回true，否则返回false
     */
    public static boolean beginTransaction(Connection connection) {

        try {
            if (connection != null  && connection.getAutoCommit()) {
                connection.setAutoCommit(false);
                return true;
            }
            return false;
        } catch (SQLException e) {
            throw new DbException("开启事务失败", e);
        }
    }

    /**
     * 提交某个connection的事务
     * @param connection
     */
    public static void commitTransaction(Connection connection) {
        try {
            if (connection != null  && !connection.getAutoCommit()) {
                connection.commit();
            }
        } catch (Exception e) {
            throw new DbException("事务提交失败", e);
        }
    }

    /**
     * 回滚事务
     * @param connection
     */
    public static void rollbackTransaction(Connection connection) {
        try {
            if(connection != null  && !connection.getAutoCommit()) {
                connection.rollback();
            }
        } catch (Exception e) {
            throw new DbException("数据库事务回滚失败", e);
        }
    }

    /**
     * 关闭一个数据库连接。该方法不会自动提交连接中的事务，也不会回滚事务，重置前请确认事务是否完结。
     * @param conn
     */
    public static void closeConnection(Connection conn) {
        if (conn != null) {
            try {
                conn.close();
//                LOGGER.trace("关闭connection：{}", conn.hashCode());
            } catch (SQLException e) {
                throw new DbException("关闭数据库连接失败", e);
            }
        }
    }


    /**
     * 手动启动一个事务，使用config中配置的默认的事务等级。<br/>
     * 一定要谨慎使用该方法，尤其注意不要内部仍调用该方法，因为每次调用都会创建一个新事务，嵌套调用、递归调用都可能导致连接池资源加速消耗。
     * @param function
     * @return
     */
    public static <T> T tx(Supplier<T> function) {
        return tx(AppConfig.getAsEnum(ConfigKey.DB_TRANSACTION_DEFAULTLEVEL, TransactionLevel.class), function);
    }

    /**
     * 手动启动一个事务<br/>
     * 一定要谨慎使用该方法，尤其注意不要内部仍调用该方法，因为每次调用都会创建一个新事务，嵌套调用、递归调用都可能导致连接池资源加速消耗。
     *
     * @param transactionLevel 事务等级，在TransactionLevel类中有定义，不要使用NONE，它代表不开启事务
     * @param function
     * @return
     */
    public static <T> T tx(TransactionLevel transactionLevel, Supplier<T> function) {

        DbSession dbSession = new DbSession();
        dbSession.setLevel(transactionLevel);
//        LOGGER.trace("开启新session：{}", dbSession.hashCode());
        DbSessionContext.insertSession(dbSession);


        try {
            T t = function.get();
            DbSessionContext.commit();
            return t;
        } catch (Throwable e) {
            DbSessionContext.rollback();
            throw new DbException(e);
        } finally {
//            LOGGER.trace("准备关闭session");
            DbSessionContext.removeSession();
        }
    }

    /**
     * 创建一个新的会话，用完一定记得clear
     * @return
     */
    public static DbSession newSession() {
        return new DbSession();
    }



    /**
     * 获取某个数据源对应的数据库类型sql执行器
     * @param dataSourceName
     * @return
     */
    public static SqlExecutor getSqlExecutor(String dataSourceName) {
        return SqlExecutor.getSqlExecutor(getDataSourceDbType(dataSourceName));
    }

    /**
     * 获取Domain类绑定的数据源
     * @param domainClass
     * @return
     */
    public static String getDataSource(Class<? extends BaseDomain> domainClass) {
        DomainDefinition definition = DomainContext.getDomainDefinition(domainClass);
        String dataSource = definition.getDataSource();
        return StringKit.isNotBlank(dataSource) ? dataSource : DataSourceContext.getMainSourceName();
    }

    /**
     * 获取数据源的数据库类型
     * @param dataSourceName
     * @return
     */
    private static DbType getDataSourceDbType(String dataSourceName) {
        return DataSourceContext.getDataSource(dataSourceName).getDbType();
    }

    /**
     * 根据Domain类获取表名，注意这个Domain类只能是单表domain
     * @param domainClass
     * @return
     */
    public static String tableName(Class domainClass) {
        DomainDefinition definition = DomainContext.getDomainDefinition(domainClass);
        if (definition.getIsSplit()) {
            throw new SqlException(domainClass.getName() + " 是分表Domain，不能通过该方法获取表名");
        }
        return definition.getTableName();
    }

    /**
     * 根据Domain类上的splitRule生成对应的表名
     * @param domainClass
     * @param args
     * @return
     */
    public static String tableName(Class domainClass, Serializable... args) {
        if (args == null || args.length == 0) {
            return tableName(domainClass);
        }
        DomainDefinition definition = DomainContext.getDomainDefinition(domainClass);
        if (definition.getIsSplit() == false) {
            throw new RuntimeException(domainClass.getName() + " 不是分表Domain，不能通过该方法获取表名");
        }
        String splitRule = definition.getSplitRule();
        for (Object arg : args) {
            splitRule = StringKit.replaceOnce(splitRule,"*", String.valueOf(arg));
        }
        return splitRule;
    }


    /**
     * 执行一条语句，这个语句可以是任意语句。使用主数据源
     * @param statement
     * @return int 受影响的行数，如果执行的是一个select语句，返回-1
     */
    public static int execute(String statement) {
        return execute(DataSourceContext.getMainSourceName(), statement, null);
    }
    /**
     * 执行一条语句，这个语句可以是任意语句。使用指定的数据源
     * @param statement
     * @return int 受影响的行数，如果执行的是一个select语句，返回-1
     */
    public static int execute(String dataSourceName, String statement) {
        return execute(dataSourceName, statement, null);
    }

    /**
     * 执行一条PreparedStatement语句，这个语句可以是任意语句。使用主数据源
     * @param statement 语句，如果传入了args，就是用preparedstatement查询，没有就用statement
     * @param args 如果是preparedstatement,这个就是要传入的值，
     * @return int 受影响的行数，如果执行的是一个select语句，返回-1
     */
    public static int execute(String statement, Serializable[] args) {
        return execute(DataSourceContext.getMainSourceName(), statement, args);
    }

    /**
     * 执行一条PreparedStatement语句，这个语句可以是任意语句。
     * @param dataSourceName 语句执行使用的数据源
     * @param statement 语句，如果传入了args，就是用preparedstatement查询，没有就用statement
     * @param args 如果是preparedstatement,这个就是要传入的值，
     * @return int 受影响的行数，如果执行的是一个select语句，返回-1
     */
    public static int execute(String dataSourceName, String statement, Serializable[] args) {
        return getSqlExecutor(dataSourceName).execute(dataSourceName, statement, args );
    }

    /**
     * 执行一个sql语句
     * @param sql
     * @return 受影响的行数，如果执行的是一个select语句，返回-1
     */
    public static int execute(Sql sql) {
        return getSqlExecutor(sql.getDataSourceName()).execute(sql);
    }


    /**
     * 通过主数据源，为Domain类创建一个表，这个表只能是单表，不能是分表
     * @param domainClass
     */
    public static void createTable(Class<? extends BaseDomain> domainClass) {
        createTable(getDataSource(domainClass), domainClass, (Serializable[]) null);
    }




    /**
     * 通过主数据源，为Domain类创建一个表，这个表只能是单表，不能是分表
     * @param domainClass
     */
    public static void createTable(Class<? extends BaseDomain> domainClass, Serializable... args) {
        createTable(getDataSource(domainClass), domainClass, args);
    }



    /**
     * 建表
     * @param dataSourceName
     * @param domainClass
     */
    public static void createTable(String dataSourceName, Class<? extends BaseDomain> domainClass) {
        createTable(dataSourceName, domainClass, (Serializable[]) null);
    }

    /**
     * 根据Domain类进行建表，如果表已存在，就不会执行
     * @param dataSourceName
     * @param domainClass
     * @param args 如果是分表，需要传入的表名参数
     */
    public static void createTable(String dataSourceName, Class<? extends BaseDomain> domainClass, Serializable... args) {
        getSqlExecutor(dataSourceName).createTable(dataSourceName, domainClass, args);
    }


    /**
     * 为Domain类修改表结构，这个表只能是单表，不能是分表.<br/>
     * 注意该方法只能为表结构添加新的字段，已存在字段不会做处理，因为可能涉及数据转换，需要你根据情况手动处理。
     * @param domainClass
     */
    public static void alterTable(Class domainClass) {
        alterTable(getDataSource(domainClass), domainClass, (Serializable[]) null);
    }
    /**
     * 为Domain类修改表结构，这个表只能是单表，不能是分表<br/>
     * 注意该方法只能为表结构添加新的字段，已存在字段不会做处理，因为可能涉及数据转换，需要你根据情况手动处理。
     * @param domainClass
     */
    public static void alterTable(Class domainClass, Serializable... args) {
        alterTable(getDataSource(domainClass), domainClass, args);
    }




    /**
     * 修改表结构<br/>
     * 注意该方法只能为表结构添加新的字段，已存在字段不会做处理，因为可能涉及数据转换，需要你根据情况手动处理。
     * @param dataSourceName
     * @param domainClass
     */
    public static void alterTable(String dataSourceName, Class domainClass) {
        alterTable(dataSourceName, domainClass, (Serializable[]) null);
    }
    /**
     * 分表修改表结构<br/>
     * 注意该方法只能为表结构添加新的字段，已存在字段不会做处理，因为可能涉及数据转换，需要你根据情况手动处理。
     * @param dataSourceName
     * @param domainClass
     * @param args
     */
    public static void alterTable(String dataSourceName, Class domainClass, Serializable... args) {
        getSqlExecutor(dataSourceName).alterTable(dataSourceName, domainClass, args);
    }



    /**
     * 对所有的单表进行建表或者alter操作。不存在就建表，存在的就alter
     */
    public static void createOrAlterAllSingleTable() {
        List<DomainDefinition> domains = DomainContext.getAllDomainDefinition();
        domains.forEach(item -> {
            if (tableExist(item.getDomainClass())) {
                alterTable(item.getDomainClass());
            } else {
                createTable(item.getDomainClass());
            }
        });
    }
    /**
     * 对所有的单表进行建表或者alter操作。不存在就建表，存在的就alter
     */
    public static void createOrAlterAllSingleTable(String dataSource) {
        List<DomainDefinition> domains = DomainContext.getAllDomainDefinition();
        domains.forEach(item -> {
            if (tableExist(dataSource, tableName(item.getDomainClass()))) {
                alterTable(dataSource, item.getDomainClass());
            } else {
                createTable(dataSource, item.getDomainClass());
            }
        });
    }


    /**
     * 对某个单表进行建表或者alter操作。不存在就建表，存在的就alter
     */
    public static void createOrAlterSingleTable(Class<? extends BaseDomain> domainClass) {
        if (tableExist(domainClass)) {
            alterTable(domainClass);
        } else {
            createTable(domainClass);
        }
    }
    /**
     * 对某个单表进行建表或者alter操作。不存在就建表，存在的就alter
     */
    public static void createOrAlterSingleTable(String dataSource, Class<? extends BaseDomain> domainClass) {
        if (tableExist(dataSource, tableName(domainClass))) {
            alterTable(dataSource, domainClass);
        } else {
            createTable(dataSource, domainClass);
        }
    }
    /**
     * 对某个分表进行建表或者alter操作。不存在就建表，存在的就alter
     */
    public static void createOrAlterMultiTable(Class<? extends BaseDomain> domainClass, Serializable... args) {
        if (tableExist(domainClass, args)) {
            alterTable(domainClass, args);
        } else {
            createTable(domainClass, args);
        }
    }
    /**
     * 对某个分表进行建表或者alter操作。不存在就建表，存在的就alter
     */
    public static void createOrAlterMultiTable(String dataSource, Class<? extends BaseDomain> domainClass, Serializable... args) {
        if (tableExist(dataSource, tableName(domainClass, args))) {
            alterTable(dataSource, domainClass, args);
        } else {
            createTable(dataSource, domainClass, args);
        }
    }

    /**
     * 表是否存在
     * @param domainClass
     * @return
     */
    public static boolean tableExist(Class domainClass) {
        return tableExist(getDataSource(domainClass), tableName(domainClass));
    }

    /**
     * 检查表是否存在
     * @param domainClass
     * @param args
     * @return
     */
    public static boolean tableExist(Class domainClass, Serializable... args) {
        return tableExist(getDataSource(domainClass), tableName(domainClass, args));
    }

    /**
     * 检查表是否存在
     * @param dataSourceName 数据源名称
     * @param tableName 表名
     * @return
     */
    public static boolean tableExist(String dataSourceName, String tableName) {
        return getSqlExecutor(dataSourceName).tableExist(dataSourceName, tableName);
    }


    /**
     * 检查表的某个字段是否 存在
     * @param dataSourceName 数据源名
     * @param tableName 表名
     * @param columnName 列名
     * @return
     */
    public static boolean columnExist(String dataSourceName, String tableName, String columnName) {
        return getSqlExecutor(dataSourceName).columnExist(dataSourceName, tableName, columnName);
    }

    /**
     * 根据一个实例插入一条记录，如果包含自增字段，语句执行完后会自动更新到实例中
     * @param instance
     * @return 如果包含自增字段，那么就返回自增的id，否则返回null
     */
    public static Object insertOne(BaseDomain instance) {
        if (instance == null) return null;
        return insertOne(getDataSource(instance.getClass()), tableName(instance.getClass()), instance);
    }


    /**
     * 根据一个实例插入一条记录，如果包含自增字段，语句执行完后会自动更新到实例中
     * @param instance
     * @param tableName 分表情况下它的表名
     * @return 如果包含自增字段，那么就返回自增的id，否则返回null
     */
    public static Object insertOne(BaseDomain instance, String tableName) {
        if (instance == null) return null;
        return insertOne(getDataSource(instance.getClass()), tableName, instance);
    }



    /**
     * 根据一个实例插入一条记录，如果包含自增字段，语句执行完后会自动更新到实例中
     * @param dataSourceName
     * @param instance
     * @return 如果有自增字段，就返回自增值，否则返回null
     */
    public static Object insertOne(String dataSourceName, BaseDomain instance) {
        if (instance == null) return null;
        return insertOne(dataSourceName, tableName(instance.getClass()), instance);
    }

    /**
     * 根据一个实例插入一条记录，如果包含自增字段，语句执行完后会自动更新到实例中
     * @param dataSourceName
     * @param tableName
     * @param instance
     * @return 如果包含自增字段，那么就返回自增的id，否则返回null
     */
    public static Object insertOne(String dataSourceName, String tableName, BaseDomain instance) {
        return getSqlExecutor(dataSourceName).insertOne(dataSourceName, tableName, instance);
    }

    /**
     * 根据一个实例插入一条记录，前提是指定列 在同表中不存在 和本次要插入的值一致的记录。
     * 如果包含自增字段，语句执行完后会自动更新到实例中
     * @param instance
     * @param checkCols 要检查的列名
     * @return 如果包含自增字段，那么就返回自增的id，否则返回null
     */
    public static Object insertOneIfNotExist(BaseDomain instance, String[] checkCols) {
        if (instance == null) return null;
        return insertOneIfNotExist(getDataSource(instance.getClass()), tableName(instance.getClass()), instance, checkCols);
    }
    /**
     * 根据一个实例插入一条记录，前提是指定列 在同表中不存在 和本次要插入的值一致的记录。
     * 如果包含自增字段，语句执行完后会自动更新到实例中
     * @param instance
     * @param tableName
     * @param checkCols 要检查的列名
     * @return 如果包含自增字段，那么就返回自增的id，否则返回null
     */
    public static Object insertOneIfNotExist(BaseDomain instance, String tableName, String[] checkCols) {
        if (instance == null) return null;
        return insertOneIfNotExist(getDataSource(instance.getClass()), tableName, instance, checkCols);
    }
    /**
     * 根据一个实例插入一条记录，前提是指定列 在同表中不存在 和本次要插入的值一致的记录。
     * 如果包含自增字段，语句执行完后会自动更新到实例中
     * @param dataSourceName
     * @param instance
     * @param checkCols 要检查的列名
     * @return 如果包含自增字段，那么就返回自增的id，否则返回null
     */
    public static Object insertOneIfNotExist(String dataSourceName, BaseDomain instance, String[] checkCols) {
        if (instance == null) return null;
        return insertOneIfNotExist(dataSourceName, tableName(instance.getClass()), instance, checkCols);
    }


    /**
     * 根据一个实例插入一条记录，前提是指定列 在同表中不存在 和本次要插入的值一致的记录。
     * 如果包含自增字段，语句执行完后会自动更新到实例中
     * @param dataSourceName
     * @param tableName
     * @param instance
     * @param checkCols 要检查的列名
     * @return 如果包含自增字段，那么就返回自增的id，否则返回null
     */
    public static Object insertOneIfNotExist(String dataSourceName, String tableName, BaseDomain instance, String[] checkCols) {
        return getSqlExecutor(dataSourceName).insertOneIfNotExist(dataSourceName, tableName, instance, checkCols);
    }
    /**
     * 向主数据源中，根据Sql查询语句，向指定表、列插入数据<br/>
     * insert into ... (...,...) select ..... from ... where ...
     * @param tableName
     * @param cols
     * @param querySql 数据来源查询语句
     * @return 返回插入的数据条数
     */
    public static int insert(String tableName, String[] cols,Sql querySql) {
        return insert(DataSourceContext.getMainSourceName(), tableName, cols, querySql);
    }
    /**
     * 向主数据源中，根据Sql查询语句，向指定表、列插入数据<br/>
     * insert into ... (...,...) select ..... from ... where ...
     * @param tableName
     * @param cols
     * @param querySql 数据来源查询语句
     * @return 返回插入的数据条数
     */
    public static int insert(String dataSourceName,String tableName, String[] cols,Sql querySql) {
        return getSqlExecutor(dataSourceName).insert(dataSourceName, tableName, cols, querySql);
    }

    /**
     * 向主数据源中，指定表、列、值进行插入数据
     * @param tableName
     * @param colAndValues 列名和值 数组，按照 列名，值，列名，值的顺序传入
     * @return 如果有自增字段，返回自增值，long类型
     */
    public static Long insert(String tableName, Serializable[] colAndValues) {
        return insert(DataSourceContext.getMainSourceName(), tableName, colAndValues);
    }
    /**
     * 指定数据源、表、列、值进行插入数据
     * @param dataSourceName
     * @param tableName
     * @param colAndValues 列名和值 数组，按照 列名，值，列名，值的顺序传入
     * @return
     */
    public static Long insert(String dataSourceName, String tableName, Serializable[] colAndValues) {
        if (colAndValues == null || colAndValues.length == 0) {
            throw new SqlException("colAndValues不能为空");
        }
        if (colAndValues.length % 2 != 0) {
            throw new SqlException("colAndValues 参数数量不正确");
        }
        String[] cols = new String[colAndValues.length / 2];
        Serializable[] values = new Serializable[colAndValues.length /2];

        for (int i = 0; i < colAndValues.length; i+=2) {
            cols[i/2] = (String)colAndValues[i];
            values[i/2] = colAndValues[i+1];
        }

        return getSqlExecutor(dataSourceName).insert(dataSourceName, tableName, cols, values);
    }
    /**
     * 指定数据源、表、列、值进行插入数据,前提是同表中，指定条件的记录不存在
     * @param tableName
     * @param insertColAndValues 要插入的列名和值 数组，按照 列名，值，列名，值的顺序传入
     * @param checkColAndValues 要检测的列名和值 数组，按照 列名，值，列名，值的顺序传入
     * @return
     */
    public static Long insertIfNotExist(String tableName, Serializable[] insertColAndValues, Serializable[] checkColAndValues) {
        return insertIfNotExist(DataSourceContext.getMainSourceName(), tableName, insertColAndValues, checkColAndValues);
    }
    /**
     * 指定数据源、表、列、值进行插入数据,前提是同表中，指定条件的记录不存在
     * @param dataSourceName
     * @param tableName
     * @param insertColAndValues 要插入的列名和值 数组，按照 列名，值，列名，值的顺序传入
     * @param checkColAndValues 要检测的列名和值 数组，按照 列名，值，列名，值的顺序传入
     * @return
     */
    public static Long insertIfNotExist(String dataSourceName, String tableName,Serializable[] insertColAndValues, Serializable[] checkColAndValues) {
        if (insertColAndValues == null || insertColAndValues.length == 0) {
            throw new SqlException("insertColAndValues不能为空");
        }
        if (insertColAndValues.length % 2 != 0) {
            throw new SqlException("insertColAndValues 参数数量不正确");
        }

        if (checkColAndValues != null && checkColAndValues.length % 2 != 0) {
            throw new SqlException("checkColAndValues 参数数量不正确");
        }

        return getSqlExecutor(dataSourceName).insertIfNotExist(dataSourceName, tableName, insertColAndValues, checkColAndValues);
    }


    /**
     * 批量插入
     * @param instances
     */
    public static List<? extends BaseDomain> insertBatch(List<? extends BaseDomain> instances) {
        return insertBatch(instances, (Serializable[]) null);
    }

    /**
     * 批量插入
     * @param instances 要插入的实例，需要是分表实例
     * @param tableNameArgs 分表表名参数
     */
    public static List<? extends BaseDomain> insertBatch(List<? extends BaseDomain> instances, Serializable... tableNameArgs) {
        if (instances == null || instances.isEmpty()) return instances;
        BaseDomain instance = instances.get(0);
        Class<? extends BaseDomain> domainClass = instance.getClass();
        return insertBatch(getDataSource(domainClass), tableName(domainClass, tableNameArgs), instances);
    }
    /**
     * 批量插入
     * @param dataSourceName 数据源
     * @param instances 要插入的实例，需要是单表实例
     */
    public static List<? extends BaseDomain> insertBatch(String dataSourceName, List<? extends BaseDomain> instances) {
        if (instances == null || instances.isEmpty()) return instances;
        BaseDomain instance = instances.get(0);
        Class<? extends BaseDomain> domainClass = instance.getClass();
        return insertBatch(dataSourceName, tableName(domainClass), instances);
    }
    /**
     * 批量插入
     * @param dataSourceName 数据源
     * @param instances 要插入的实例，需要是单表实例
     * @param tableNameArgs 分表表名参数
     */
    public static List<? extends BaseDomain> insertBatch(String dataSourceName, List<? extends BaseDomain> instances, Serializable... tableNameArgs) {
        if (instances == null || instances.isEmpty()) return instances;
        BaseDomain instance = instances.get(0);
        Class<? extends BaseDomain> domainClass = instance.getClass();
        return insertBatch(dataSourceName, tableName(domainClass,tableNameArgs), instances);
    }

    /**
     * 批量插入
     * @param dataSourceName
     * @param tableName
     * @param instances
     */
    public static List<? extends BaseDomain> insertBatch(String dataSourceName, String tableName, List<? extends BaseDomain> instances) {
        return insertBatch(dataSourceName, tableName, instances, 500);
    }


    /**
     * 批量插入
     * @param dataSourceName
     * @param tableName
     * @param instances
     * @param batchCount 分批插入，每次批量插入的数量，防止某些语句太长，一次数量太大导致插入失败
     */
    public static List<? extends BaseDomain> insertBatch(String dataSourceName, String tableName, List<? extends BaseDomain> instances, int batchCount) {
        return getSqlExecutor(dataSourceName).insertBatch(dataSourceName, tableName, instances, batchCount);
    }


    /**
     * 根据colAndValues拼接成一个 xxx=a and yyy = b and...的语句，然后返回匹配到的第一条数据
     * @param domainClass
     * @param colAndValues 参数必须是2的整数倍，按照column,value,column,value。。。的顺序传入.<br/>column必须为String类型。
     * @param <T>
     * @return
     */
    public static <T> T getOne(Class<? extends BaseDomain> domainClass, Serializable[] colAndValues) {
        return getOne(getDataSource(domainClass),tableName(domainClass), domainClass, colAndValues);
    }
    /**
     * 根据colAndValues拼接成一个 xxx=a and yyy = b and...的语句，然后返回匹配到的第一条数据
     * @param tableName 表名
     * @param resultClass 封装返回结果的类
     * @param colAndValues 参数必须是2的整数倍，按照column,value,column,value。。。的顺序传入.<br/>column必须为String类型。
     * @param <T>
     * @return
     */
    public static <T> T getOne(String tableName,Class resultClass, Serializable[] colAndValues) {
        return getOne(DataSourceContext.getMainSourceName(),tableName, resultClass, colAndValues);
    }

    /**
     * 根据colAndValues拼接成一个 xxx=a and yyy = b and...的语句，然后返回匹配到的第一条数据
     * @param tableName 表名
     * @param resultClass 封装返回结果的类
     * @param colAndValues 参数必须是2的整数倍，按照column,value,column,value。。。的顺序传入.<br/>column必须为String类型。
     * @param <T>
     * @return
     */
    public static <T> T getOne(String dataSourceName,String tableName, Class resultClass, Serializable[] colAndValues) {
        if (colAndValues != null && colAndValues.length % 2 != 0) {
            throw new SqlException("colAndValues对应的参数必须是2的整数倍，按照column,value,column,value。。。的顺序传入");
        }
        Sql sql = Sql.build(dataSourceName, tableName);
        if (colAndValues != null) {
            for (int i = 0; i < colAndValues.length; i+=2) {
                sql.eq((String) colAndValues[i], colAndValues[i+1]);

            }
        }
        sql.setDomainClass(resultClass);
        return getOne(sql);
    }

    /**
     * 根据sql语句获取第一条数据
     * @param sql
     * @param <T>
     * @return
     */
    public static <T> T getOne(Sql sql) {
        return (T) getOne(sql.getDataSourceName(), sql.getStatement(), sql.getArgs(), sql.getDomainClass());
    }


    /**
     * 执行一条查询语句，获取第一行结果，并自动转换为resultClass类的对象
     * @param statement 要执行的查询语句
     * @param args 执行语句需要传入的参数
     * @param resultClass 将结果转换为的类
     * @param <T>
     * @return
     */
    public static <T> T getOne(String statement, Serializable[] args, Class<T> resultClass) {
        return getSqlExecutor(DataSourceContext.getMainSourceName()).getOne(DataSourceContext.getMainSourceName(), statement, args, resultClass);
    }

    /**
     * 执行一条查询语句，获取第一行数据，并自动转换为resultClass类的对象
     * @param dataSourceName 使用的数据源名称
     * @param statement 要执行的查询语句
     * @param args 执行语句需要传入的参数
     * @param resultClass 将结果转换为的类,支持Domain类、POJO类、Kv、Map、HashMap、LinkedHashMap
     * @param <T>
     * @return
     */
    public static <T> T getOne(String dataSourceName, String statement, Serializable[] args, Class<T> resultClass) {
        return getSqlExecutor(dataSourceName).getOne(dataSourceName, statement, args, resultClass);
    }








    /**
     * 根据id从数据库取出数据，并转换成对应类型的对象
     * @param dataSourceName
     * @param tableName
     * @param id
     * @param resultClass 封装查询结果的类
     * @param <T>
     * @return
     */
    public static <T> T getById(String dataSourceName, String tableName, Serializable id,  Class resultClass) {
        return getOne(Sql.build(dataSourceName, tableName).setDomainClass(resultClass).eq("id", id));
    }
    /**
     * 根据id从数据库取出数据，并转换成对应类型的对象
     * @param tableName
     * @param id
     * @param resultClass 封装查询结果的类
     * @param <T>
     * @return
     */
    public static <T> T getById(String tableName, Serializable id,  Class resultClass) {
        return getOne(Sql.build(tableName).setDomainClass(resultClass).eq("id", id));
    }
    /**
     * 根据id从数据库取出数据，并转换成对应类型的对象
     * @param domainClass
     * @param id
     * @param <T>
     * @return
     */
    public static <T> T getById(Class<? extends BaseDomain> domainClass, Serializable id) {
        return getOne(Sql.build(domainClass).eq("id", id));
    }

    /**
     * 根据id从数据库取出数据，并转换成对应类型的对象
     * @param dataSourceName 数据源名称
     * @param domainClass
     * @param id
     * @param <T>
     * @return
     */
    public static <T> T getById(String dataSourceName, Class<? extends BaseDomain> domainClass, Serializable id) {
        return getOne(Sql.build(dataSourceName, domainClass).eq("id", id));
    }



    /**
     * 根据colAndValues拼接成一个 xxx=a and yyy = b and...的语句，然后返回匹配到的数据集合
     * @param domainClass
     * @param colAndValues 参数必须是2的整数倍，按照column,value,column,value。。。的顺序传入.<br/>column必须为String类型。
     * @param <T>
     * @return
     */
    public static <T> List<T> getList(Class<? extends BaseDomain> domainClass, Serializable[] colAndValues) {
        return getList(getDataSource(domainClass),tableName(domainClass), domainClass, colAndValues);
    }
    /**
     * 根据colAndValues拼接成一个 xxx=a and yyy = b and...的语句，然后返回匹配到的数据集合
     * @param tableName 表名
     * @param resultClass 封装返回结果的类
     * @param colAndValues 参数必须是2的整数倍，按照column,value,column,value。。。的顺序传入.<br/>column必须为String类型。
     * @param <T>
     * @return
     */
    public static <T> List<T> getList(String tableName,Class resultClass, Serializable[] colAndValues) {
        return getList(DataSourceContext.getMainSourceName(),tableName, resultClass, colAndValues);
    }

    /**
     * 根据colAndValues拼接成一个 xxx=a and yyy = b and...的语句，然后返回匹配到的数据集合
     * @param tableName 表名
     * @param resultClass 封装返回结果的类
     * @param colAndValues 参数必须是2的整数倍，按照column,value,column,value。。。的顺序传入.<br/>column必须为String类型。
     * @param <T>
     * @return
     */
    public static <T> List<T> getList(String dataSourceName,String tableName, Class resultClass, Serializable[] colAndValues) {
        if (colAndValues != null && colAndValues.length % 2 != 0) {
            throw new SqlException("colAndValues对应的参数必须是2的整数倍，按照column,value,column,value。。。的顺序传入");
        }
        Sql sql = Sql.build(dataSourceName, tableName);
        if (colAndValues != null) {
            for (int i = 0; i < colAndValues.length; i+=2) {
                sql.eq((String) colAndValues[i], colAndValues[i+1]);

            }
        }
        sql.setDomainClass(resultClass);
        return getList(sql);
    }

    /**
     * 执行查询语句，获取数据，并自动转换为resultClass类的对象集合
     * @param sql
     * @param <T>
     * @return
     */
    public static <T> List<T> getList(Sql sql) {
        return getList(sql.getDataSourceName(), sql.getStatement(), sql.getArgs(), sql.getDomainClass());
    }



    /**
     * 执行分页查询语句，获取数据，并自动转换为resultClass类的对象集合
     * @param sql
     * @param pageNum 分页页数，从1开始
     * @param pageSize 分页每页数据量
     * @param <T>
     * @return
     */
    public static <T> Pager<T> getListByPage(Sql sql, int pageNum, int pageSize) {
        return getListByPage(sql, Pager.build(pageNum, pageSize));
    }

    /**
     * 执行分页查询语句，获取数据，并自动转换为resultClass类的对象集合
     * @param sql
     * @param pager 分页对象，根据它进行分页处理，如果为null，就相当于不分页返回所有结果
     * @param <T>
     * @return
     */
    public static <T> Pager<T> getListByPage(Sql sql, Pager pager) {
        if (pager == null) {
            pager = new Pager();
        } else {
            sql.page(pager.getPageNum(), pager.getPageSize());
        }
        List list = getList(sql);
        pager.setList(list);
        pager.setTotalCount(getCount(sql));
        return pager;
    }




    /**
     * 执行查询语句，获取数据，并自动转换为resultClass类的对象集合
     * @param statement 要执行的查询语句
     * @param args 执行语句需要传入的参数
     * @param resultClass 将结果转换为的类,支持Domain类、POJO类、Kv、Map、HashMap、LinkedHashMap
     * @param <T>
     * @return
     */
    public static <T> List<T> getList(String statement, Serializable[] args, Class<T> resultClass) {
        return getList(DataSourceContext.getMainSourceName(), statement, args, resultClass);
    }
    /**
     * 执行查询语句，获取数据，并自动转换为resultClass类的对象集合
     * @param dataSourceName 使用的数据源名称
     * @param statement 要执行的查询语句
     * @param args 执行语句需要传入的参数
     * @param resultClass 将结果转换为的类,支持Domain类、POJO类、Kv、Map、HashMap、LinkedHashMap
     * @param <T>
     * @return
     */
    public static <T> List<T> getList(String dataSourceName, String statement, Serializable[] args, Class<T> resultClass) {
        return getSqlExecutor(dataSourceName).getList(dataSourceName, statement, args, resultClass);
    }




    /**
     * 执行一条查询语句，获取第一行某一列的数据，并自动转换为columnClass类的对象
     * @param sql 要执行的查询语句
     * @param columnClass 将结果转换为的类,支持基本数据类型及包装类和他们的数组、String和String数组、Date、Time、byte[]
     * @param <T>
     * @return
     */
    public static <T> T getColumn(Sql sql, Class<T> columnClass) {
        return getSqlExecutor(sql.getDataSourceName()).getColumn(sql.getDataSourceName(), sql.getStatement(), sql.getArgs(), columnClass);
    }
    /**
     * 执行一条查询语句，获取第一行某一列的数据，并自动转换为columnType类的对象.适用于转换复杂的json格式数据
     * @param sql 要执行的查询语句
     * @param columnType 将结果转换为的类,jackson提供的反序列化类
     * @param <T>
     * @return
     */
    public static <T> T getColumn(Sql sql, JavaType columnType) {
        return getSqlExecutor(sql.getDataSourceName()).getColumn(sql.getDataSourceName(), sql.getStatement(), sql.getArgs(), columnType);
    }


    /**
     * 执行一条查询语句，获取第一行某一列的数据，并自动转换为columnClass类的对象
     * @param statement 要执行的查询语句
     * @param args 执行语句需要传入的参数
     * @param columnClass 将结果转换为的类,支持基本数据类型及包装类和他们的数组、String和String数组、Date、Time、byte[]
     * @param <T>
     * @return
     */
    public static <T> T getColumn(String statement, Serializable[] args, Class<T> columnClass) {
        return getSqlExecutor(DataSourceContext.getMainSourceName()).getColumn(DataSourceContext.getMainSourceName(), statement, args, columnClass);
    }
    /**
     * 执行一条查询语句，获取第一行某一列的数据，并自动转换为columnClass类的对象.适用于转换复杂的json格式数据
     * @param statement 要执行的查询语句
     * @param args 执行语句需要传入的参数
     * @param columnType 将结果转换为的类,jackson提供的反序列化类
     * @param <T>
     * @return
     */
    public static <T> T getColumn(String statement, Serializable[] args, JavaType columnType) {
        return getSqlExecutor(DataSourceContext.getMainSourceName()).getColumn(DataSourceContext.getMainSourceName(), statement, args, columnType);
    }

    /**
     * 执行一条查询语句，获取第一行某一列的数据，并自动转换为columnClass类的对象
     * @param dataSourceName 使用的数据源名称
     * @param statement 要执行的查询语句
     * @param args 执行语句需要传入的参数
     * @param columnClass 将结果转换为的类,支持基本数据类型及包装类和他们的数组、String和String数组、Date、Time、byte[]
     * @param <T>
     * @return
     */
    public static <T> T getColumn(String dataSourceName, String statement, Serializable[] args, Class<T> columnClass) {
        return getSqlExecutor(dataSourceName).getColumn(dataSourceName, statement, args, columnClass);
    }
    /**
     * 执行一条查询语句，获取第一行某一列的数据，并自动转换为columnClass类的对象.适用于转换复杂的json格式数据
     * @param dataSourceName 使用的数据源名称
     * @param statement 要执行的查询语句
     * @param args 执行语句需要传入的参数
     * @param columnType 将结果转换为的类,jackson提供的反序列化类
     * @param <T>
     * @return
     */
    public static <T> T getColumn(String dataSourceName, String statement, Serializable[] args, JavaType columnType) {
        return getSqlExecutor(dataSourceName).getColumn(dataSourceName, statement, args, columnType);
    }


    /**
     * sum统计，sql可以只拼接条件语句
     * @param sql
     * @return
     */
    public static int getCount(Sql sql) {
        return getSqlExecutor(sql.getDataSourceName()).getCount(sql);
    }


    /**
     * 通过sql语句检查是否存在符合条件的记录。
     * @param tableName 要查询的表
     * @param colAndValues 条件列和值，会自动拼接=。要求参数数量必须是2的整数倍，按照column,value,column,value。。。的顺序传入.<br/>column必须为String类型。
     * @return
     */
    public static boolean checkExist(String tableName, Serializable[] colAndValues) {
        return checkExist(DataSourceContext.getMainSourceName(), tableName, colAndValues);
    }
    /**
     * 通过sql语句检查是否存在符合条件的记录。
     * @param domainClass 要查询的表对应的domain类
     * @param colAndValues 条件列和值，会自动拼接=。要求参数数量必须是2的整数倍，按照column,value,column,value。。。的顺序传入.<br/>column必须为String类型。
     * @return
     */
    public static boolean checkExist(Class<? extends BaseDomain> domainClass, Serializable[] colAndValues) {
        return checkExist(getDataSource(domainClass), tableName(domainClass), colAndValues);
    }

    /**
     * 通过sql语句检查是否存在符合条件的记录。
     * @param dataSourceName 数据源
     * @param tableName 表名
     * @param colAndValues 条件列和值，会自动拼接=。要求参数数量必须是2的整数倍，按照column,value,column,value。。。的顺序传入.<br/>column必须为String类型。
     * @return
     */
    public static boolean checkExist(String dataSourceName, String tableName, Serializable[] colAndValues) {
        return checkExistExcept(dataSourceName, tableName, colAndValues, null, null);
    }


    /**
     * 通过sql语句检查是否存在符合条件的记录。
     * @param tableName 要查询的表
     * @param colAndValues 条件列和值，会自动拼接=。要求参数数量必须是2的整数倍，按照column,value,column,value。。。的顺序传入.<br/>column必须为String类型。
     * @param exceptColumn 要排除记录的列名
     * @param exceptValue 要排除记录的值
     * @return
     */
    public static boolean checkExistExcept(String tableName, Serializable[] colAndValues, String exceptColumn, Serializable exceptValue) {
        return checkExistExcept(DataSourceContext.getMainSourceName(), tableName, colAndValues, exceptColumn, exceptValue);
    }
    /**
     * 通过sql语句检查是否存在符合条件的记录。
     * @param domainClass 要查询的表对应的domain类
     * @param colAndValues 条件列和值，会自动拼接=。要求参数数量必须是2的整数倍，按照column,value,column,value。。。的顺序传入.<br/>column必须为String类型。
     * @param exceptColumn 要排除记录的列名
     * @param exceptValue 要排除记录的值
     * @return
     */
    public static boolean checkExistExcept(Class<? extends BaseDomain> domainClass, Serializable[] colAndValues, String exceptColumn, Serializable exceptValue) {
        return checkExistExcept(getDataSource(domainClass), tableName(domainClass), colAndValues, exceptColumn, exceptValue);
    }

    /**
     * 通过sql语句检查是否存在符合条件的记录，并排除掉某记录。
     * @param dataSourceName 数据源
     * @param tableName 表名
     * @param colAndValues 条件列和值，会自动拼接=。要求参数数量必须是2的整数倍，按照column,value,column,value。。。的顺序传入.<br/>column必须为String类型。
     * @param exceptColumn 要排除记录的列名
     * @param exceptValue 要排除记录的值
     * @return
     */
    public static boolean checkExistExcept(String dataSourceName, String tableName, Serializable[] colAndValues, String exceptColumn, Serializable exceptValue) {
        if (colAndValues != null && colAndValues.length % 2 != 0) {
            throw new SqlException("colAndValues对应的参数必须是2的整数倍，按照column,value,column,value。。。的顺序传入");
        }
        Sql sql = Sql.build(dataSourceName, tableName);
        if (colAndValues != null) {
            for (int i = 0; i < colAndValues.length; i+=2) {
                sql.eq((String) colAndValues[i], colAndValues[i+1]);
            }
        }
        if (exceptColumn != null) {
            if (exceptValue == null) {
//                sql.isNotNull(exceptColumn);
            } else {
                sql.ueq(exceptColumn, exceptValue);
            }
        }
        return checkExist(sql);
    }

    /**
     * 通过sql语句检查是否存在符合条件的记录。sql只负责拼接条件即可
     * @param sql
     * @return
     */
    public static boolean checkExist(Sql sql) {
        sql.page(1);
        return getCount(sql) > 0;
    }



    /**
     * sum统计，sql可以只拼接条件语句
     * @param columnName 要统计的列名
     * @param sql 条件语句
     * @return
     */
    public static int getSum(String columnName, Sql sql) {
        return getSqlExecutor(sql.getDataSourceName()).getSum(columnName, sql, int.class);
    }

    /**
     * sum统计，sql可以只拼接条件语句
     * @param columnName 要统计的列名
     * @param sql 条件语句
     * @return 以double类型返回sum的值
     */
    public static double getSumAsDouble(String columnName, Sql sql) {
        return getSqlExecutor(sql.getDataSourceName()).getSum(columnName, sql, double.class);
    }




    /**
     * 根据实例更新数据库数据。要求实例必须主键有值
     * @param instance
     * @return
     */
    public static int updateOne(BaseDomain instance) {
        if (instance == null) return 0;
        return updateOne(getDataSource(instance.getClass()), tableName(instance.getClass()), instance);
    }


    /**
     * 根据实例更新数据库中信息，需要实例的主键必须有值
     * @param tableName
     * @param instance
     * @return
     */
    public static int updateOne(String tableName, BaseDomain instance) {
        if (instance == null) return 0;
        return updateOne(getDataSource(instance.getClass()), tableName, instance);
    }
    /**
     * 根据实例更新数据库中信息，需要实例的主键必须有值
     * @param dataSourceName
     * @param tableName
     * @param instance
     * @return
     */
    public static int updateOne(String dataSourceName, String tableName, BaseDomain instance) {
        return getSqlExecutor(dataSourceName).updateOne(dataSourceName, tableName, instance);
    }


    /**
     * 根据id值更新对应记录的数据。colAndValues 必须按照 列名，值，列名，值...的格式传入
     * @param domainClass
     * @param id
     * @param colAndValues 要更新的列名，值，列名，值...
     * @return
     */
    public static int updateById(Class<? extends BaseDomain> domainClass, Serializable id, Serializable[] colAndValues) {
        return updateById(getDataSource(domainClass), tableName(domainClass), id, colAndValues);
    }

    /**
     * 根据id值更新对应记录的数据。colAndValues 必须按照 列名，值，列名，值...的格式传入
     * @param tableName
     * @param id
     * @param colAndValues 要更新的列名，值，列名，值...
     * @return
     */
    public static int updateById(String tableName, Serializable id, Serializable[] colAndValues) {
        return updateById(DataSourceContext.getMainSourceName(), tableName, id, colAndValues);
    }

    /**
     * 根据id值更新对应记录的数据。colAndValues 必须按照 列名，值，列名，值...的格式传入
     * @param dataSourceName
     * @param tableName
     * @param id
     * @param colAndValues 要更新的列名，值，列名，值...
     * @return
     */
    public static int updateById(String dataSourceName, String tableName, Serializable id, Serializable[] colAndValues) {
        if (id == null) return 0;
        if (colAndValues == null ) return 0;
        if (colAndValues.length %2 != 0) {
            throw new SqlException("colAndValues参数数量不对，需要按照列名、值、列名、值...的格式成对传入");
        }
        Sql sql = Sql.build(dataSourceName, tableName).eq("id", id);
        for (int i = 0; i < colAndValues.length; i+=2) {
            sql.set(colAndValues[i], colAndValues[i+1]);

        }
        return update(sql);
    }
    /**
     * 执行更新语句
     * @param sql
     * @return
     */
    public static int update(Sql sql) {
        sql.update();
        return getSqlExecutor(sql.getDataSourceName()).update(sql.getDataSourceName(), sql.getStatement(), sql.getArgs());
    }

    /**
     * 执行更新语句
     * @param statement
     * @param args 执行语句需要传入的参数
     * @return 影响的数据行数
     */
    public static int update(String statement, Serializable[] args) {
        return update(DataSourceContext.getMainSourceName(), statement, args);
    }
    /**
     * 执行更新语句
     * @param dataSourceName
     * @param statement
     * @param args 执行语句需要传入的参数
     * @return 影响的数据行数
     */
    public static int update(String dataSourceName, String statement, Serializable[] args) {
        return getSqlExecutor(dataSourceName).update(dataSourceName, statement, args);
    }

    /**
     * 批量更新数据
     * @param instances
     * @return
     */
    public static int updateBatch(List<? extends BaseDomain> instances) {
        if (instances == null || instances.isEmpty()) return 0;
        Class<? extends BaseDomain> clazz = instances.get(0).getClass();
        return updateBatch(getDataSource(clazz), tableName(clazz), instances);
    }

    /**
     * 批量更新数据
     * @param tableName
     * @param instances
     * @return
     */
    public static int updateBatch(String tableName, List<? extends BaseDomain> instances) {
        if (instances == null || instances.isEmpty()) return 0;
        Class<? extends BaseDomain> clazz = instances.get(0).getClass();
        return updateBatch(getDataSource(clazz), tableName, instances);

    }

    /**
     * 批量更新
     * @param dataSourceName
     * @param tableName
     * @param instances
     * @return
     */
    public static int updateBatch(String dataSourceName, String tableName, List<? extends BaseDomain> instances) {
        return updateBatch(dataSourceName, tableName, instances, 500);
    }

    /**
     * 批量更新
     * @param dataSourceName
     * @param tableName
     * @param instances
     * @param batchCount 每次批量执行多少条
     * @return
     */
    public static int updateBatch(String dataSourceName, String tableName, List<? extends BaseDomain> instances, int batchCount) {
        return getSqlExecutor(dataSourceName).updateBatch(dataSourceName, tableName, instances, batchCount);
    }




    /**
     * 主键有值就根据主键值找到对应记录更新其数据，
     * 没有主键或者主键值为null，就插入
     * @param instance
     * @return
     */
    public static BaseDomain saveOne(BaseDomain instance) {
        if (instance == null) return null;
        Class<? extends BaseDomain> clazz = instance.getClass();
        return saveOne(getDataSource(clazz), tableName(clazz), instance );
    }


    /**
     * 主键有值就根据主键值找到对应记录更新其数据，
     * 没有主键或者主键值为null，就插入
     * @param tableName
     * @param instance
     * @return
     */
    public static BaseDomain saveOne(String tableName, BaseDomain instance) {
        if (instance == null) return null;
        return saveOne(getDataSource(instance.getClass()), tableName, instance );
    }

    /**
     * 主键有值就根据主键值找到对应记录更新其数据，
     * 没有主键或者主键值为null，就插入
     * @param dataSourceName
     * @param tableName
     * @param instance
     * @return
     */
    public static BaseDomain saveOne(String dataSourceName, String tableName, BaseDomain instance) {
        return getSqlExecutor(dataSourceName).saveOne(dataSourceName, tableName, instance);


    }





    /**
     * 批量保存数据，注意该方法效率并不高，不是批量执行语句，因为需要对每一个实例进行判断要insert还是update。
     * 所以尽量不要用该方法。
     * @param instances
     * @return
     */
    public static List<? extends BaseDomain> saveBatch(List<? extends BaseDomain> instances) {
        if (instances == null || instances.isEmpty()) return instances;
        Class<? extends BaseDomain> clazz = instances.get(0).getClass();
        return saveBatch(getDataSource(clazz), tableName(clazz), instances);
    }

    /**
     * 批量保存数据，注意该方法效率并不高，
     * 所以尽量不要用该方法。
     * @param tableName
     * @param instances
     * @return
     */
    public static List<? extends BaseDomain> saveBatch(String tableName, List<? extends BaseDomain> instances) {
        if (instances == null || instances.isEmpty()) return instances;
        String dataSource = getDataSource(instances.get(0).getClass());
        return saveBatch(dataSource, tableName, instances);

    }
    /**
     * 批量保存数据，注意该方法效率并不高，
     * 所以尽量不要用该方法。
     * @param dataSourceName
     * @param tableName
     * @param instances
     * @return
     */
    public static List<? extends BaseDomain> saveBatch(String dataSourceName, String tableName, List<? extends BaseDomain> instances) {
        return getSqlExecutor(dataSourceName).saveBatch(dataSourceName, tableName, instances, 500);

    }
    /**
     * 批量插入
     * @param dataSourceName
     * @param tableName
     * @param instances
     * @param batchCount 分批插入，每次批量插入的数量，防止某些语句太长，一次数量太大导致插入失败
     */
    public static List<? extends BaseDomain> saveBatch(String dataSourceName, String tableName, List<? extends BaseDomain> instances, int batchCount) {
        return getSqlExecutor(dataSourceName).saveBatch(dataSourceName, tableName, instances, batchCount);
    }



    /**
     * 据实例删除，实例必须有主键，并且主键必须有值
     * @param instance
     * @return
     */
    public static int delOne(BaseDomain instance) {
        if (instance == null) return 0;
        Class<? extends BaseDomain> clazz = instance.getClass();
        return delOne(getDataSource(clazz), tableName(clazz), instance );
    }

    /**
     * 据实例删除，实例必须有主键，并且主键必须有值
     * @param tableName
     * @param instance
     * @return
     */
    public static int delOne(String tableName, BaseDomain instance) {
        if (instance == null) return 0;
        return delOne(getDataSource(instance.getClass()), tableName, instance);
    }


    /**
     * 根据实例删除，实例必须有主键，并且主键必须有值
     * @param dataSourceName
     * @param tableName
     * @param instance
     * @return
     */
    public static int delOne(String dataSourceName, String tableName, BaseDomain instance) {
        return getSqlExecutor(dataSourceName).delOne(dataSourceName, tableName, instance);

    }

    /**
     * 执行删除语句
     * @param sql
     * @return
     */
    public static int del(Sql sql) {
        return getSqlExecutor(sql.getDataSourceName()).del(sql);
    }


    /**
     * 根据id字段的值进行删除。必须保证表中有id字段
     * @param domainClass
     * @param id
     * @return
     */
    public static int delById(Class<? extends BaseDomain> domainClass, Serializable id) {
        return delById(getDataSource(domainClass), tableName(domainClass), id);
    }

    /**
     * 根据id字段的值进行删除。必须保证表中有id字段
     * @param tableName
     * @param id
     * @return
     */
    public static int delById(String tableName, Serializable id) {
        return delById(DataSourceContext.getMainSourceName(), tableName, id);
    }



    /**
     * 根据id字段的值进行删除。必须保证表中有id字段
     * @param dataSourceName
     * @param tableName
     * @param id
     * @return
     */
    public static int delById(String dataSourceName, String tableName, Serializable id) {
        return del(Sql.build(dataSourceName, tableName).eq("id", id));
    }

    /**
     * 判断给定的值是否有效，布尔只要不为null，就有效，其他基本数据类型值必须>0，字符串不为空，数组不能为null且长度>0，其他不能为null
     * @param value
     * @return
     */
    public static boolean isOk(Object value) {
        if (value == null) {
            return false;
        } else {
            Class clazz = value.getClass();
            if (clazz.equals(Boolean.class) || clazz.equals(boolean.class)) {
                return true ;
            } else if (clazz.equals(Integer.class) || clazz.equals(int.class)) {
                return (Integer)value > 0;
            } else if (clazz.equals(String.class)) {
                return StringKit.isNotBlank(value.toString());
            } else if (clazz.equals(Double.class) || clazz.equals(double.class)) {
                return (Double)value > 0;
            } else if (clazz.equals(Long.class) || clazz.equals(long.class)) {
                return (Long)value > 0;
            } else if (clazz.equals(Short.class) || clazz.equals(short.class)) {
                return (Short)value > 0;
            } else if (clazz.equals(Byte.class) || clazz.equals(byte.class)) {
                return (Byte)value > 0;
            } else if (clazz.equals(Float.class) || clazz.equals(float.class)) {
                return (Float)value > 0;
            } else if (clazz.isArray()) {
                return value != null && Array.getLength(value) > 0;
            }
        }
        return true;
    }

    /**
     * 判断给定的值是否无效，布尔只要为null，就无效，其他基本数据类型值<=0，字符串为空，数组为null或>0，其他为null
     * @param value
     * @return
     */
    public static boolean isNotOk(Object value) {
        return isOk(value) == false;
    }

    /**
     * 方便的构造一个参数数组
     * @param args
     * @return
     */
    public static <T> T[] args(T... args) {
        return args;
    }

    /**
     * 分析一个domain对象，有哪些列，以及它们的值
     * @param instance
     * @param generatePrimaryKeyValue 是否生成主键值
     */
    public static List<ColAndValue> analyseDomainColAndValues(BaseDomain instance, boolean generatePrimaryKeyValue) {
        DomainDefinition definition = DomainContext.getDomainDefinition(instance.getClass());
        if (definition.getType() != DomainType.TABLE) {
            throw new SqlException(instance.getClass() + "不是表结构，无法执行操作");
        }
        List<ColumnDefinition> columns = definition.getNormalColumnList();
        List<ColAndValue> result = new LinkedList<>();
        try {
            String[] nullColumns = instance.getNullColumns();
            for (ColumnDefinition column : columns) {
                ColAndValue item = new ColAndValue();
                item.column = column;
                item.name = column.getName();
                result.add(item);

                if (column.getIsPrimaryKey()) { //主键字段不能为null：自增不需要管，让数据库自己生成，uuid的我们生成，signed直接读instance的值，如果传入了一个null值，数据库自然会报错

                    item.isPrimaryKey = true;

                    Serializable columnValue = (Serializable) column.getField().get(instance);
                    if (columnValue != null &&
                            (
                                    columnValue instanceof Number == false
                                            || ((Number)columnValue).intValue() != 0
                            )
                    ) {
                        item.value = columnValue;
                        continue;
                    }
                    //不需要生成主键值，就跳过吧
                    if (generatePrimaryKeyValue == false) continue;

                    switch (column.getGenerateFrom()) {
                        case AUTOINCREMENT:
                            item.value = null;
                            break;
                        case UUID:
                            String uuid = IdUtil.fastSimpleUUID();
                            item.value = uuid;
                            column.getField().set(instance, uuid); //将生成的uuid更新到实例上
                            break;
                        case SNOWFLAKE:
                            Serializable snowValue = null;
                            Snowflake snowflake = IdUtil.getSnowflake(1, 1);
                            if (column.getField().getType() == Long.class || column.getField().getType() == long.class) {
                                snowValue = snowflake.nextId();
                            } else if (column.getField().getType() == String.class) {
                                snowValue = snowflake.nextIdStr();
                            }
                            item.value = snowValue;
                            column.getField().set(instance, snowValue); //将生成的uuid更新到实例上
                            break;

                        case SIGNED:
                            //上面已经优先处理了
                            break;
                    }
                } else {
                    item.isPrimaryKey = false;

                    //如果值是null并且显式指定为null
                    if (ArrayUtil.contains(nullColumns, column.getName()) ) {
                        item.value = null;

                    } else {
                        Serializable value = (Serializable)column.getField().get(instance);
                        item.value = value == null ? Exp.new_("default") : value;

                    }

                }


            }
            if (result.isEmpty()) {
                throw new SqlException("实例所有字段都未指定值，操作失败");
            }
            return result;
        } catch (Throwable e) {
            if (e instanceof RuntimeException) {
                throw (RuntimeException)e;
            }
            throw new SqlException("插入数据时发生异常", e);
        }
    }

    /**
     * 列以及它的值
     */
    public static class ColAndValue {
        public String name;
        public Serializable value;
        public boolean isPrimaryKey;
        public ColumnDefinition column;


    }


}
